/*
	NounPlanet.cpp
	(c)1999 PaleStar, Richard Lyle
*/


#include "Debug/Assert.h"
#include "Debug/Profile.h"
#include "Render3d/NodeMesh.h"
#include "Render3d/NodeComplexMesh.h"
#include "Render3d/NodeComplexMesh2.h"
#include "World/VerbChat.h"
#include "GadgetShield.h"
#include "NounPlanet.h"
#include "NounShip.h"
#include "NounUnit.h"
#include "NounStructure.h"
#include "NounProjectile.h"
#include "NounStar.h"
#include "StructureDefense.h"
#include "StructureDepot.h"
#include "StructurePort.h"
#include "StructureHub.h"
#include "StructureFactory.h"
#include "Constants.h"
#include "VerbSetFlags.h"
#include "VerbScrap.h"
#include "VerbDestroyShip.h"
#include "VerbPlanetEvent.h"
#include "VerbRevolt.h"
#include "VerbDistress.h"
#include "VerbDisband.h"
#include "VerbCapturePlanet.h"
#include "VerbTradeRoute.h"
#include "VerbTransferResource.h"
#include "VerbEnterOrbit.h"
#include "Resource.h"
#include "GameContext.h"
#include "ShipPlatform.h"

//----------------------------------------------------------------------------

const int		PRODUCTION_UPDATE	= TICKS_PER_SECOND * 10;
const int		ENCONOMY_UPDATE		= TICKS_PER_SECOND * 60;
const int		EVENT_UPDATE		= TICKS_PER_SECOND * 300;
const int		CONTACTS_UPDATE		= TICKS_PER_SECOND * 5;
const float		HEX_SIZE = 6.0f;		// physical size of a hex
const float		HEX_HALF = HEX_SIZE * 0.5f;
const int		DISTRESS_MESSAGE_DELAY = TICKS_PER_SECOND * 60;		// how often to send distress messages
const dword		MAX_NOUNS_PER_HEX = 4;

static Constant	PLANET_SCANNER_RANGE( "PLANET_SCANNER_RANGE", 2000.0f );
static Constant	PLANET_SENSOR_RANGE( "PLANET_SENSOR_RANGE", 750.0f );
static Constant	PLANET_VIEW_RANGE( "PLANET_VIEW_RANGE", 50.0f );

static Constant	HEX_STACK_SPACE( "HEX_STACK_SPACE", 0.75f );		// spacing between stacked nouns
static Constant	PLANET_DEFAULT_RV( "PLANET_DEFAULT_RV", (PI * 2) / (60 * 30) );
static Constant	PLANET_DAMAGE_RADIUS( "PLANET_DAMAGE_RADIUS", 20.0f / 5000.0f );
static Constant PLANET_MAX_DAMAGE_RADIUS( "PLANET_MAX_DAMAGE_RADIUS", 12.0f );

// for each population killed, how much does it lower moral
static Constant	POPULATION_KILLED_MORAL( "POPULATION_KILLED_MORAL", 0.025f );
// how fast does moral increase per turn
static Constant	PLANET_MORAL_INCREASE( "PLANET_MORAL_INCREASE", 0.01f );
// how much of the population is killed by plague per turn
static Constant	PLAGUE_POPULATION_KILLED( "PLAGUE_POPULATION_KILLED", 0.15f );
// technology += (population * TECHNOLOGY_POPULATION)
static Constant	TECHNOLOGY_POPULATION( "TECHNOLOGY_POPULATION", 0.05f );		
// technology decay rate..
static Constant TECHNOLOGY_DECAY_RATE( "TECHNOLOGY_DECAY_RATE", 0.1f );

static Constant PLANET_BASE_DEMAND( "PLANET_BASE_DEMAND", 0.1f );
// how do structures affect to the total resource demand for a planet
static Constant PLANET_STRUCTURE_DEMAND( "PLANET_STRUCTURE_DEMAND", 0.05f );
// how do friendly units affect a planets total resource demand
static Constant PLANET_UNIT_DEMAND( "PLANET_UNIT_DEMAND", 0.01f );
// how does population affect the resource demand
static Constant PLANET_POPULATION_DEMAND( "PLANET_POPULATION_DEMAND", 0.05f );
// for each factory, demand is increased by this amount
static Constant PLANET_FACTORY_DEMAND( "PLANET_FACTORY_DEMAND", 0.5f );
// modify the demand levels if the planet has a shipyard
static Constant PLANET_SHIPYARD_DEMAND( "PLANET_SHIPYARD_DEMAND", 20.0f );
// chance planet will become blockaded
static Constant PLANET_BLOCKADE_CHANCE( "PLANET_BLOCKADE_CHANCE", 25.0f );

//----------------------------------------------------------------------------

IMPLEMENT_FACTORY( NounPlanet, NounBody );
REGISTER_FACTORY_KEY( NounPlanet, 4083315223543632941 );

BEGIN_PROPERTY_LIST( NounPlanet, NounBody );
	CLEAR_PROPERTY_FLAGS( m_DetectionFlags, PF_UPDATE );
	ADD_TRANSMIT_PROPERTY( m_Name );
	ADD_TRANSMIT_PROPERTY( m_fRotVelocity );
	ADD_TRANSMIT_UPDATE_PROPERTY( m_nTeamId );
	ADD_TRANSMIT_UPDATE_PROPERTY( m_Children );
	ADD_TRANSMIT_UPDATE_PROPERTY( m_fPopulation );
	ADD_TRANSMIT_UPDATE_PROPERTY( m_fTechnology );
	ADD_TRANSMIT_UPDATE_PROPERTY( m_fMoral );
	ADD_TRANSMIT_UPDATE_PROPERTY( m_nLockRank );
	ADD_TRANSMIT_UPDATE_PROPERTY( m_TradeRoutes );
	ADD_TRANSMIT_UPDATE_PROPERTY( m_fResources );
END_PROPERTY_LIST();

//----------------------------------------------------------------------------

NounPlanet::NounPlanet() :
	m_fRotVelocity( PLANET_DEFAULT_RV ),
	m_fPopulation( 0.0f ),
	m_fTechnology( 0.0f ),
	m_nLastDistress( 0 ),
	m_nLastEvent( 0 ),
	m_fMoral( 1.0f ),
	m_nLockRank( 0 ),
	m_fResources( 1000.0f ),
	m_fResourceDemand( 1.0f ),
	m_nContactsTick( 0 )
{}

//---------------------------------------------------------------------------------------------------

bool NounPlanet::read( InStream & input )
{
	if (! NounBody::read( input ) )
		return false;

	return true;
}

//----------------------------------------------------------------------------

NounType NounPlanet::nounType() const
{
	return TYPE_LANDMARK;
}

bool NounPlanet::collidable() const
{
	return true;
}

bool NounPlanet::canOrbit() const
{
	return true;		
}

bool NounPlanet::canAttachCargo( Noun * pCargo ) const
{
	if ( maxUnits() <= 0 )
		return false;		// don't allow anything to be unloaded onto infernos or gas planets..

	// make sure the cargo has a context, or isEnemy / isFriend won't work correctly!
	if (! pCargo->context() )
		pCargo->setContext( context() );

	if ( WidgetCast<NounUnit>( pCargo ) )
	{
		if ( isFriend( pCargo ) )
			return friendlyUnitCount() < maxUnits();
		else
			return enemyUnitCount() < maxUnits();
	}

	return true;
}

float NounPlanet::baseSignature() const
{
	return radius() * 100.0f;
}

//----------------------------------------------------------------------------

void NounPlanet::setup()
{
	NounBody::setup();

	setResources( 1000.0f );
}

void NounPlanet::initialize()
{
	NounBody::initialize();

	m_nContactsTick = tick() + seed( CONTACTS_UPDATE );
	m_nProductionTick = tick() + seed( PRODUCTION_UPDATE );
	m_nEconomyTick = tick() + seed( ENCONOMY_UPDATE );
}

void NounPlanet::calculateRadius()
{
	m_fRadius = 1.0f;
	// get the mesh for this planet
	Scene * pScene = nounContext()->scene();
	if ( pScene != NULL )
	{
		// set radius to maximum extent
		m_fRadius = 32768.0f;
		// find the terrain object in our scene
		if ( findTerrain( pScene->node(), Matrix33( true ), Vector3( true ) ) )
		{
			// look for an existing path map object inside the scene of this planet, if not found then create one dynamically
			m_pMap = PathMap::findMap( m_pTerrain );
			if ( m_pMap )
			{
				// now that we have a valid hex map, snap all units and structures to their hexes... 
				for(int i=0;i<childCount();++i)
				{
					BaseNode * pChild = child( i );
					if ( WidgetCast<NounUnit>( pChild ) )
						((NounUnit *)pChild)->updatePosition();
					else if ( WidgetCast<NounStructure>( pChild ) )
						((NounStructure *)pChild)->updatePosition();
				}
			}
		}
	}
	m_fRadius2 = m_fRadius * m_fRadius;
}

void NounPlanet::release()
{
	// unregister this planet with the star
	if ( maxUnits() > 0 )
	{
		NounStar * pStar = WidgetCast<NounStar>( orbitRoot() );
		if ( pStar != NULL )
			pStar->removePlanet( this );
	}

	m_Contacts.release();
	m_TradeRoutes.release();
	m_pTerrain = NULL;
	m_pMap = NULL;

	NounBody::release();
}

void NounPlanet::simulate( dword nTick )
{
	// rotate this planet on it's axis
	if ( m_fRotVelocity != 0.0f )
	{
		int nElapsed = nTick - tick();
		float fElapsed = TICK_DURATION_S * nElapsed;
		setOrientation( orientation() + Euler( 0.0f, m_fRotVelocity * fElapsed, 0.0f ) );
		invalidateWorld();
	}

	// update planetary contacts
	if ( nTick >= m_nContactsTick )
	{
		m_nContactsTick = nTick + CONTACTS_UPDATE;

		//PROFILE_START( "NounPlanet::simulate( dword nTick ) - Update Contacts" );
		bool bEnemyDetected = false;

		m_Contacts.release();
		// update the contacts
		Array< GameContext::NounCollision > detect;
		if ( context()->proximityCheck( worldPosition(), PLANET_SCANNER_RANGE, detect ) )
		{
			for(int i=0;i<detect.size();i++)
			{
				Noun * pNoun = detect[i].pNoun;
				if ( canDetect( pNoun ) )
				{
					if (!bEnemyDetected && WidgetCast<NounShip>( pNoun ) && isEnemy( pNoun ) && !WidgetCast<ShipPlatform>( pNoun ) )
						bEnemyDetected = true;

					m_Contacts.push( pNoun );
				}
			}
		}

		if ( server() )
		{
			// handle the enemy detected flag
			if ( bEnemyDetected && (flags() & FLAG_ENEMY) == 0 )
			{
				Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::ENEMY, true ) );
				Verb::Ref( new VerbDistress( this, VerbDistress::ENEMY ) );
			}
			else if ( !bEnemyDetected && (flags() & FLAG_ENEMY) != 0 )
			{
				Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::ENEMY, false ) );
			}
		}

		//PROFILE_END();
	}

	if ( nTick >= m_nProductionTick )
	{
		m_nProductionTick = nTick + PRODUCTION_UPDATE;
		// push the path finding cache for this planet, to save some memory...
		if ( m_pMap.valid() )
			m_pMap->flushPathCache();

		// check for workers / power
		int technology = (int)floor( m_fTechnology );
		int workers = (int)floor( m_fPopulation );
		int workerSum = 0;
		int power = powerAvailable();
		int powerSum = 0;

		// if this is a habitable planet, register this planet with our root star
		if ( maxUnits() > 0 )
		{
			NounStar * pStar = WidgetCast<NounStar>( orbitRoot() );
			if ( pStar != NULL )
				pStar->addPlanet( this );
		}

		bool bTechnologyShortage = false;
		// clear all structure flags
		dword nStructureFlags = 0;

		// check structures on the planet
		for(int j=0;j<childCount();j++)
		{
			NounStructure * pStructure = WidgetCast<NounStructure>( child(j) );
			if ( pStructure != NULL )
			{
				workerSum += pStructure->workers();
				powerSum += pStructure->power();

				if ( pStructure->flags() & NounStructure::FLAG_ACTIVE )
				{
					// structure is on, make sure it can be on...
					if ( pStructure->workers() > 0 )
					{
						if ( pStructure->workers() <= workers )
							pStructure->clearFlags( NounStructure::FLAG_WORKER_SHORT );
						else
							pStructure->addFlags( NounStructure::FLAG_WORKER_SHORT );
					}
					if ( pStructure->power() < 0 )
					{
						if ( (pStructure->power() + power) >= 0 )
							pStructure->clearFlags( NounStructure::FLAG_POWER_SHORT );
						else
							pStructure->addFlags( NounStructure::FLAG_POWER_SHORT );
					}
					if ( pStructure->technology() > 0 )
					{
						if ( pStructure->technology() > technology )
						{
							bTechnologyShortage = true;
							pStructure->addFlags( NounStructure::FLAG_TECH_SHORT );
						}
						else
							pStructure->clearFlags( NounStructure::FLAG_TECH_SHORT );
					}
				}

				// OR structure flags if the structure is active
				if ( pStructure->active() )
				{
					nStructureFlags |= pStructure->planetFlags();
					workers -= pStructure->workers();
					power += pStructure->power();
				}

			}
		}

		// have structure flags changed, if true then send out a verb to all clients/servers with the new flags
		if ( server() && (flags() & FLAG_STRUCTURES) != nStructureFlags )
		{
			clearFlags( FLAG_STRUCTURES );
			addFlags( nStructureFlags );

			// send verb to all clients to update the flags for this planet
			Verb::Ref( new VerbSetFlags( this, flags() ) );
		}


		//// build structures on the planet
		//for(j=0;j<childCount();j++)
		//{
		//	NounStructure * pStructure = WidgetCast<NounStructure>( child(j) );
		//	if ( pStructure != NULL && pStructure->isBuilding() )
		//		pStructure->build( 10 );		// change this if PRODUCTION_UPDATE value is changed
		//}

		if ( server() )
		{
			bool powerShortage = powerSum < 0;
			bool workerShortage = workerSum > m_fPopulation;

			if ( powerShortage && (flags() & FLAG_POWER_SHORT) == 0 )
				Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::POWER_SHORT, true) );
			else if ( !powerShortage && (flags() & FLAG_POWER_SHORT) != 0 )
				Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::POWER_SHORT, false) );

			if ( workerShortage && (flags() & FLAG_WORKER_SHORT) == 0 )
				Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::WORKER_SHORT, true) );
			else if ( !workerShortage && (flags() & FLAG_WORKER_SHORT) != 0 )
				Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::WORKER_SHORT, false) );

			if ( bTechnologyShortage && (flags() & FLAG_TECH_SHORT) == 0 )
				Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::TECH_SHORT, true ) );
			else if ( !bTechnologyShortage && (flags() & FLAG_TECH_SHORT) != 0 )
				Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::TECH_SHORT, false ) );
		}

		// do mine production
		addResources( mining() * production() );
	}

	if ( nTick >= m_nEconomyTick )
	{
		m_nEconomyTick = nTick + ENCONOMY_UPDATE;
		// maximum technology level is based on the amount of available research
		int nMaxTechnology = research();

		// if plague, then kill of 25% of the population
		if ( (flags() & FLAG_PLAGUE) != 0 && m_fPopulation > 0 )
			killPopulation( m_fPopulation * PLAGUE_POPULATION_KILLED );

		int foodProduction = food();
		int foodNeed = foodNeeded();
		int nMaxPopulation = habitat();
		bool bFoodShortage = foodProduction < foodNeed;

		if (!bFoodShortage )
		{
			// grow population
			if ( foodProduction > foodNeed && m_fPopulation < nMaxPopulation )
				m_fPopulation = Clamp<float>( m_fPopulation + 1.0f, 0.0f, nMaxPopulation );		// food surplus, grow population...

			// increase moral
			m_fMoral = Min( m_fMoral + PLANET_MORAL_INCREASE, 1.0f );
		}
		else 
		{
			// if not enough food is being produced...
			killPopulation( 1 );
		}


		// clamp population, it can be no larger than the total available habitat
		if ( m_fPopulation > nMaxPopulation )
			killPopulation( m_fPopulation - nMaxPopulation );

		// increase technology level based on the planet population
		if ( m_fTechnology > nMaxTechnology )
		{
			// decay the technology level ...
			m_fTechnology -= m_fTechnology * TECHNOLOGY_DECAY_RATE;
		}
		else
		{
			// increase technology level up to max...
			m_fTechnology += TECHNOLOGY_POPULATION * m_fPopulation;
			if ( m_fTechnology > nMaxTechnology )
				m_fTechnology = nMaxTechnology;
		}

		// repair structures
		for(int j=0;j<childCount();j++)
		{
			NounStructure * pStructure = WidgetCast<NounStructure>( child(j) );
			if ( pStructure != NULL && pStructure->active() )
				pStructure->repair();
		}

		// disband excess ground units
		int unitCount = 0;
		for(j=0;j<childCount();j++)
		{
			NounUnit * pUnit = WidgetCast<NounUnit>( child(j) );
			if ( pUnit != NULL && isFriend( pUnit ) )
			{
				unitCount++;
				if ( unitCount > maxUnits() )
					Verb::Ref( new VerbDisband( pUnit, this ) );
			}
		}

		// update the demand for resources
		if ( maxResources() > 0 )
			m_fResourceDemand = 1.0f - (m_fResources / maxResources());
		else
			m_fResourceDemand = 0.0f;

		// remove invalid trade routes
		for(j=tradeRoutes()-1;j>=0;j--)
		{
			NounPlanet * pRoute = tradeRoute( j );
			if (! VerbTradeRoute::canTradeRoute( this, pRoute, NULL ) )
				removeTradeRoute( j );
		}

		// random events
		if ( server() )
		{
			if ( bFoodShortage && (flags() & FLAG_FOOD_SHORT) == 0 )
				Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::FOOD_SHORT, true) );
			else if ( !bFoodShortage && (flags() & FLAG_FOOD_SHORT) != 0 )
				Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::FOOD_SHORT, false) );

			// end blockades when all enemy ships are gone
			if ( (flags() & FLAG_ENEMY) == 0 && (flags() & FLAG_BLOCKADE) != 0 )
			{
				Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::BLOCKADE, false ) );
			}
			else if ( (flags() & FLAG_ENEMY) != 0 && (flags() & FLAG_BLOCKADE) == 0 && (rand() % 100) < PLANET_BLOCKADE_CHANCE )
			{
				Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::BLOCKADE, true ) );
				Verb::Ref( new VerbDistress( this, VerbDistress::BLOCKADE ) );
			}

			// send distress signal if enemy units are on the planet
			int enemyUnits = enemyUnitCount();
			if ( enemyUnits > 0 )
				Verb::Ref( new VerbDistress( this, VerbDistress::INVASION ) );

			// check for random events every 5 minutes
			if ( (m_nLastEvent + EVENT_UPDATE) < nTick )
			{
				m_nLastEvent = nTick;

				// only let plague run for one round, always stop, other events are more long term
				if ( (flags() & FLAG_PLAGUE) != 0 )
					Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::PLAGUE, false ) );

				int check = rand() % 7;
				switch( check )
				{
				case 0:
					// PLAGUE event
					if ( (rand() % 100) > 98 && m_fPopulation > 50 )
						Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::PLAGUE, true ) );	// plague!
					break;
				case 1:
					if ( (flags() & FLAG_REVOLUTION) == 0 )
					{
						int roll = rand() % 100;
						int chance = (m_fMoral * 100) + 40;
						if ( roll > chance )
							Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::REVOLUTION, true ) );
					}
					else
					{
						int roll = rand() % 100;
						int chance = (m_fMoral * 100) + 50;
						if ( roll > chance )
							Verb::Ref( new VerbRevolt( this, -1 ) );
						else if ( roll < 25 )
							Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::REVOLUTION, false ) );
					}
					break;
				case 2:
					// RESESSION event
					if ( (flags() & FLAG_RESESSION) == 0 )
					{
						int roll = rand() % 100;
						int chance = (m_fMoral * 100) + 50;
						if ( roll > chance )
							Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::RESESSION, true ) );	// resession
					}
					else
						Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::RESESSION, false ) );	// resession
					break;
				case 3:
					// STRIKE event
					if ( (flags() & FLAG_STRIKE) == 0 )
					{
						int roll = rand() % 100;
						int chance = (m_fMoral * 100) + 50;
						if ( roll > chance )
							Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::STRIKE, true ) );
					}
					else
						Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::STRIKE, false ) );
					break;
				case 4:
					// BOOM event
					if ( (flags() & FLAG_BOOM) == 0 )
					{
						int roll = rand() % 100;
						int chance = (m_fMoral * 100) - 97;
						if ( roll < chance )
							Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::BOOM, true) );
					}
					else
						Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::BOOM, false) );
					break;
				case 5:
					// SMUGGLERS event
					if ( (flags() & FLAG_SMUGGLERS) == 0 )
					{
						int roll = rand() % 100;
						int chance = (m_fMoral * 100) + 50;
						if ( roll > chance )
							Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::SMUGGLERS, true) );
					}
					else
						Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::SMUGGLERS, false) );
					break;
				case 6:
					// MINING_FIND event
					if ( (flags() & FLAG_MINING_FIND) == 0 )
					{
						int roll = rand() % 100;
						if ( roll > 98 )
							Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::MINING_FIND, true) );
					}
					else
						Verb::Ref( new VerbPlanetEvent( this, VerbPlanetEvent::MINING_FIND, false) );
					break;
				}
			}
		}
	}

	NounBody::simulate( nTick );
}

void NounPlanet::collision( Noun * pCollide, const Vector3 & intersect )
{
	// destroy our ship if we hit a planet..
	if ( WidgetCast<NounShip>( pCollide ) && pCollide->isLocal() 
		&& !((NounShip *)pCollide)->destroyed() )
	{
		Verb::Ref( new VerbDestroyShip( (NounShip *)pCollide, this, true ) );
	}
}

bool NounPlanet::canDamage( dword type ) const
{
	return (type & DAMAGE_PLANET) != 0;
}

void NounPlanet::inflictDamage( dword nWhen, Noun * pFrom, int damage, dword type, const Vector3 & direction )
{
	if ( server() )
	{
		if ( isEnemy( pFrom ) )
		{
			if ( (m_nLastDistress + DISTRESS_MESSAGE_DELAY) < context()->tick() )
			{
				m_nLastDistress = context()->tick();
				Verb::Ref( new VerbDistress( this, VerbDistress::ATTACKED ) );
			}
		}
	}

	// deflect damage, check for structures with shields
	if ( type & (DAMAGE_ENERGY|DAMAGE_KINETIC) )
	{
		for(int i=0;i<childCount();i++)
		{
			NounStructure * pStructure = WidgetCast<NounStructure>( child(i) );
			if ( pStructure != NULL && pStructure->active() )
			{
				for(int j=0;j<pStructure->childCount();j++)
				{
					GadgetShield * pShield = WidgetCast<GadgetShield>( pStructure->child(j) );
					if ( pShield != NULL )
					{
						pShield->addFlags( NounGadget::FLAG_MOUNT_FULL );
						damage = pShield->deflect( type, damage, FACING_FRONT, direction );
						if ( damage <= 0 )
							return;
					}
				}
			}
		}
	}

	// kill population
	if ( m_fPopulation > 0.0f )
	{
		// decimate the population, the higher the population the faster and easier it's killed
		float fDP = Max( 200000.0f / m_fPopulation, 1000.0f );
		float fKilled = (float)damage / fDP;

		killPopulation( fKilled );

		if ( m_fPopulation <= 0 )
		{
			// all population has been killed
			if ( server() )
				globalChat( CharString().format( "<color;ffffff>Comms: %s: colony has been destroyed!", name() ) );
			setTeamId( 0 );
		}
	}

	// reduce resources
	if ( m_fResources > 0.0f )
	{
		m_fResources -= (float)damage / (50000.0f / m_fResources);
		if ( m_fResources <= 0 )
			m_fResources = 0.0f;
	}

	Vector3 P( direction );
	P.normalize();
	P *= radius();

	//TRACE( CharString().format("NounPlanet::inflictDamage, position = %.1f,%.1f,%.1f", P.x,P.y,P.z) );

	float damageRadius = Clamp<float>( 1.0f + (PLANET_DAMAGE_RADIUS * damage), 1.0f, PLANET_MAX_DAMAGE_RADIUS );
	
	// cause damage to units on the surface
	for(int i=0;i<childCount();i++)
	{
		NounGame * pNoun = WidgetCast<NounGame>( child(i) );
		if (!pNoun || !pNoun->canDamage( type ) )
			continue;		// can't damage this noun with this type of damage

		float distance = (pNoun->position() - P).magnitude();
		if ( distance < damageRadius )
			pNoun->inflictDamage( nWhen, pFrom, (1.0f - (distance / damageRadius)) * damage, type, direction );
	}
}

CharString NounPlanet::status() const
{
	CharString sTargetInfo( NounBody::status() );
	sTargetInfo += CharString().format("%s - %s\n", teamName(), description() );
	sTargetInfo += "<color;c0c0c0>";

	//sTargetInfo += CharString().format("\n%s", pPlanet->resourceNames() );
	if ( population() > 0 )
		sTargetInfo += CharString().format("Population: %d\n", ((int)population()) );
	int units = unitCount();
	if ( units > 0 )
		sTargetInfo += CharString().format("Units: %d\n", units );

	int structures = 0;
	int shield = 0;
	int defense = 0;
	int depots = 0;
	int ports = 0;

	for(int i=0;i<childCount();i++)
	{
		BaseNode * pChild = child(i);
		if ( WidgetCast<NounStructure>( pChild ) )
		{
			structures++;

			if ( WidgetCast<StructureDefense>( pChild ) )
			{
				defense++;

				int shielding = ((StructureDefense *)pChild)->shielding();
				if ( shielding >= 0 )
				{
					shield += shielding;
					if ( shield > 0 )
						shield = (shield + shielding) >> 1;		// average them together
					else
						shield = shielding;
				}
			}
			else if ( WidgetCast<StructureDepot>( pChild ) )
				depots++;
			else if ( WidgetCast<StructurePort>( pChild ) )
				ports++;
		}
	}

	if ( structures > 0 )
		sTargetInfo += CharString().format("Structures: %d", structures );
	if ( defense > 0 )
		sTargetInfo += CharString().format(", Def: %d", defense);
	if ( depots > 0 )
		sTargetInfo += CharString().format(", Dep: %d", depots );
	if ( ports > 0 )
		sTargetInfo += CharString().format(", Port: %d", ports );
	sTargetInfo += "\n";

	if ( shield > 0 )
		sTargetInfo += CharString().format("Shield: %d%%\n", shield);
	sTargetInfo += CharString().format("Resources: %s\n", FormatNumber<char,int>( resources() ) );

	return sTargetInfo;
}

int NounPlanet::resourceCount() const
{
	return NounGame::resourceCount() + static_cast<int>( m_fResources );
}

int NounPlanet::useResources( int nWanted )
{
	int nUsed = NounGame::useResources( nWanted );
	nWanted -= nUsed;

	int nTake = Min( (int)m_fResources, nWanted );
	m_fResources -= nTake;
	nUsed += nTake;

	return nUsed;
}

//----------------------------------------------------------------------------

const char * NounPlanet::description() const
{
	return "Planet";
}

int NounPlanet::baseValue() const
{
	return 0;
}

int NounPlanet::baseHabitat() const
{
	return 0;
}

int NounPlanet::baseFood() const
{
	return 0;
}

int NounPlanet::maxUnits() const
{
	return 32;
}

int NounPlanet::maxStructures() const
{
	return 32;
}

int NounPlanet::maxResources() const
{
	return 100000;
}

//----------------------------------------------------------------------------

void NounPlanet::setRotVelocity( float rv )
{
	m_fRotVelocity = rv;
}

void NounPlanet::setPopulation( float fPopulation )
{
	m_fPopulation = fPopulation;
}

void NounPlanet::setTechnology( float fTechnology )
{
	m_fTechnology = fTechnology;
}

void NounPlanet::setResources( float fResources )
{
	m_fResources = Clamp<float>( fResources, 0, maxResources() );
}

float NounPlanet::addResources( float fProduced )
{
	m_fResources = Clamp<float>( m_fResources + fProduced, 0, maxResources() );
	return m_fResources;
}

void NounPlanet::setMoral( float moral )
{
	m_fMoral = moral;
}

void NounPlanet::setLockRank( int rank )
{
	m_nLockRank = rank;
}

void NounPlanet::addTradeRoute( NounPlanet * pPlanet )
{
	m_TradeRoutes.push( pPlanet );
	updateVersion();
}

void NounPlanet::removeTradeRoute( int n )
{
	m_TradeRoutes.remove( n );
	updateVersion();
}

void NounPlanet::removeTradeRoute( NounPlanet * pPlanet )
{
	m_TradeRoutes.removeSearch( pPlanet );
	updateVersion();
}

float NounPlanet::killPopulation( float fKill )
{
	fKill = Clamp<float>( fKill, 0, m_fPopulation );
	if ( fKill > 0 )
	{
		// decrease moral based on the population killed
		m_fMoral = Max( 0.0f, m_fMoral - (POPULATION_KILLED_MORAL * fKill) );
		// decrease technology level
		m_fTechnology = m_fPopulation > 0 ? Max( m_fTechnology - ((m_fTechnology / m_fPopulation) * fKill), 0.0f ) : 0;
		// decrease the population
		m_fPopulation -= fKill;
	}

	return m_fPopulation;
}

//----------------------------------------------------------------------------

int NounPlanet::value() const
{
	return baseValue() + (population() * 1000) + (structureCount() * 5000);
}

int NounPlanet::workersAvailable() const
{
	return m_fPopulation;
}

int NounPlanet::workersNeeded() const
{
	int workers = 0;
	for(int i=0;i<childCount();i++)
	{
		NounStructure * pStructure = WidgetCast<NounStructure>( child(i) );
		if ( pStructure != NULL )
			workers += pStructure->workers();
	}

	return workers;
}

int NounPlanet::power() const
{
	int power = 0;
	for(int i=0;i<childCount();i++)
	{
		NounStructure * pStructure = WidgetCast<NounStructure>( child(i) );
		if ( pStructure != NULL && pStructure->active() )
			power += pStructure->power();
	}

	return power;
}

int NounPlanet::powerAvailable() const
{
	int power = 0;
	for(int i=0;i<childCount();i++)
	{
		NounStructure * pStructure = WidgetCast<NounStructure>( child(i) );
		if ( pStructure != NULL && pStructure->active() && pStructure->power() > 0 )
			power += pStructure->power();
	}

	return power;
}

int NounPlanet::powerUsed() const
{
	int power = 0;
	for(int i=0;i<childCount();i++)
	{
		NounStructure * pStructure = WidgetCast<NounStructure>( child(i) );
		if ( pStructure != NULL && pStructure->active() && pStructure->power() < 0 )
			power -= pStructure->power();
	}

	return power;
}

int NounPlanet::powerNeeded() const
{
	int power = 0;
	for(int i=0;i<childCount();i++)
	{
		NounStructure * pStructure = WidgetCast<NounStructure>( child(i) );
		if ( pStructure != NULL && pStructure->power() < 0 )
			power -= pStructure->power();
	}

	return power;
}

int NounPlanet::food() const
{
	int food = baseFood();
	for(int i=0;i<childCount();i++)
	{
		NounStructure * pStructure = WidgetCast<NounStructure>( child(i) );
		if ( pStructure != NULL && pStructure->active() )
			food += pStructure->food();
	}

	return food;
}

int NounPlanet::foodNeeded() const
{
	return m_fPopulation;
}

int NounPlanet::habitat() const
{
	int habitat = baseHabitat();
	for(int i=0;i<childCount();i++)
	{
		NounStructure * pStructure = WidgetCast<NounStructure>( child(i) );
		if ( pStructure != NULL && pStructure->active() )
			habitat += pStructure->habitat();
	}

	return habitat;
}

int NounPlanet::mining() const
{
	int mining = 0;
	for(int i=0;i<childCount();i++)
	{
		NounStructure * pStructure = WidgetCast<NounStructure>( child(i) );
		if ( pStructure != NULL && pStructure->active() )
			mining += pStructure->mining();
	}

	if ( flags() & FLAG_MINING_FIND)
		mining = mining << 2;	// mining * 4
	if ( flags() & FLAG_STRIKE )
		mining = mining >> 1;	// mining / 2

	return mining;
}

int NounPlanet::research() const
{
	int research = 0;
	for(int i=0;i<childCount();i++)
	{
		NounStructure * pStructure = WidgetCast<NounStructure>( child(i) );
		if ( pStructure != NULL && pStructure->active() )
			research += pStructure->research();
	}

	return research;
}

int NounPlanet::production() const
{
	int nProduction = 0;
	for(int i=0;i<childCount();i++)
	{
		NounStructure * pStructure = WidgetCast<NounStructure>( child(i) );
		if ( pStructure != NULL && pStructure->active() )
			nProduction += pStructure->production();
	}

	if ( flags() & FLAG_BOOM )
		nProduction *= 2;
	if ( flags() & FLAG_SMUGGLERS )
		nProduction /= 2;

	return nProduction;
}

bool NounPlanet::isPowerShortage() const
{
	return (flags() & FLAG_POWER_SHORT) != 0;
}

bool NounPlanet::isFoodShortage() const
{
	return (flags() & FLAG_FOOD_SHORT) != 0;
}

bool NounPlanet::isWorkerShortage() const
{
	return (flags() & FLAG_WORKER_SHORT) != 0;
}

bool NounPlanet::canGovern( Noun * pWho ) const
{
	if ( WidgetCast<NounShip>( pWho ) )
	{
		NounShip * pShip = (NounShip *)pWho;
		if ( pShip->factionId() == factionId() && pShip->rank() >= m_nLockRank )
			return true;		// yes, same faction and higher rank
	}

	return false;
}


bool NounPlanet::canBuild( Noun * pWho ) const
{
	if ( canGovern( pWho ) && structureCount() < maxStructures())
		return true;

	return false;
}

bool NounPlanet::canDetect( Noun * pNoun ) const
{
	return NounGame::canDetect( pNoun, PLANET_SENSOR_RANGE, PLANET_VIEW_RANGE );
}

CharString NounPlanet::resourceNames() const
{
	CharString sNames;
	if ( flags() & FLAG_HYDROGEN )
		sNames += CharString().format("%s ", flagName( FLAG_HYDROGEN ) );
	if ( flags() & FLAG_OXYGEN )
		sNames += CharString().format("%s ", flagName( FLAG_OXYGEN ) );
	if ( flags() & FLAG_METALS )
		sNames += CharString().format("%s ", flagName( FLAG_METALS ) );
	if ( flags() & FLAG_HEAVY_METALS )
		sNames += CharString().format("%s ", flagName( FLAG_HEAVY_METALS ) );
	if ( flags() & FLAG_CYRO_METALS )
		sNames += CharString().format("%s ", flagName( FLAG_CYRO_METALS ) );
	if ( flags() & FLAG_HYPER_MATTER )
		sNames += CharString().format("%s ", flagName( FLAG_HYPER_MATTER ) );
	if ( flags() & FLAG_DARK_MATTER )
		sNames += CharString().format("%s ", flagName( FLAG_DARK_MATTER ) );
	if ( flags() & FLAG_URDANIAM )
		sNames += CharString().format("%s ", flagName( FLAG_URDANIAM ) );
	
	return sNames;
}

int NounPlanet::unitCount() const
{
	int unitCount = 0;
	for(int i=0;i<childCount();i++)
	{
		BaseNode * pNode = child(i);
		if ( WidgetCast<NounUnit>( pNode ) )
			unitCount++;
	}

	return unitCount;
}

int NounPlanet::friendlyUnitCount() const
{
	int unitCount = 0;
	for(int i=0;i<childCount();i++)
	{
		BaseNode * pNode = child(i);
		if ( WidgetCast<NounUnit>( pNode ) && isFriend( (Noun *)pNode ) )
			unitCount++;
	}

	return unitCount;
}

int NounPlanet::enemyUnitCount() const
{
	int unitCount = 0;
	for(int i=0;i<childCount();i++)
	{
		BaseNode * pNode = child(i);
		if ( WidgetCast<NounUnit>( pNode ) && isEnemy( (Noun *)pNode ))
			unitCount++;
	}

	return unitCount;
}

int NounPlanet::structureCount() const
{
	int nStructureCount = 0;
	for(int i=0;i<childCount();i++)
		if ( WidgetCast<NounStructure>( child(i) ) )
			nStructureCount++;

	return nStructureCount;
}

int NounPlanet::factoryCount() const
{
	int nFactoryCount = 0;
	for(int i=0;i<childCount();i++)
		if ( WidgetCast<StructureFactory>( child(i) ) )
			nFactoryCount++;

	return nFactoryCount;
}

int NounPlanet::findHex( const Vector3 & vNear, bool bStructure )
{
	int nBestHex = -1;
	float fBestDistance = 0.0f;

	for(int i=0;i<hexCount();++i)
	{
		float fDistance = (vNear - hex(i).position).magnitude();
		if ( nBestHex >= 0 && fDistance > fBestDistance )
			continue;

		bool bOccupied = false;
		if ( bStructure )
		{
			// make sure no structures are already in this hex...
			for(int j=0;j<childCount() && !bOccupied;++j)
				if ( WidgetCast<NounStructure>( child(j) ) )
					bOccupied = ((NounStructure *)child(j))->hex() == i;
		}
		else
		{
			// count the number of units in the hex...
			int nCount = 0;
			for(int j=0;j<childCount() && nCount < MAX_NOUNS_PER_HEX;++j)
			{
				if ( WidgetCast<NounUnit>( child(j) ) )
				{
					NounUnit * pUnit = (NounUnit *)child(j);
					if ( pUnit->hex() == i )
						nCount++;
				}
			}

			bOccupied = nCount >= MAX_NOUNS_PER_HEX;
		}

		if (! bOccupied )
		{
			nBestHex = i;
			fBestDistance = fDistance;
		}
	}

	return nBestHex;
}

//----------------------------------------------------------------------------

const char * NounPlanet::flagName( dword nFlags )
{
	if ( nFlags & FLAG_HYDROGEN )
		return "HYDROGEN";
	else if ( nFlags & FLAG_OXYGEN )
		return "OXYGEN";
	else if ( nFlags & FLAG_METALS )
		return "METALS";
	else if ( nFlags & FLAG_HEAVY_METALS )
		return "HEAVY METALS";
	else if ( nFlags & FLAG_CYRO_METALS )
		return "CYRO METALS";
	else if ( nFlags & FLAG_HYPER_MATTER )
		return "HYPER MATTER";
	else if ( nFlags & FLAG_DARK_MATTER )
		return "DARK MATTER";
	else if ( nFlags & FLAG_URDANIAM )
		return "URDANIAM";
	else if ( nFlags & FLAG_PLAGUE )
		return "<color;0000ff>PLAGUE</color>";
	else if ( nFlags & FLAG_REVOLUTION )
		return "<color;0000ff>REVOLUTION</color>";
	else if ( nFlags & FLAG_RESESSION )
		return "<color;0000ff>RESESSION</color>";
	else if ( nFlags & FLAG_STRIKE )
		return "<color;0000ff>STRIKE</color>";
	else if ( nFlags & FLAG_BOOM )
		return "<color;2FFFAD>BOOM</color>";
	else if ( nFlags & FLAG_SMUGGLERS )
		return "<color;0000ff>SMUGGLERS</color>";
	else if ( nFlags & FLAG_MINING_FIND )
		return "<color;2FFFAD>MINING FIND</color>";
	else if ( nFlags & FLAG_FOOD_SHORT )
		return "<color;0000ff>FOOD SHORTAGE</color>";
	else if ( nFlags & FLAG_POWER_SHORT )
		return "<color;0000ff>POWER SHORTAGE</color>";
	else if ( nFlags & FLAG_WORKER_SHORT )
		return "<color;0000ff>WORKER SHORTAGE</color>";
	else if ( nFlags & FLAG_TECH_SHORT )
		return "<color;0000ff>TECHNOLOGY SHORTAGE</color>";
	else if ( nFlags & FLAG_HAS_DEPOT )
		return "DEPOT";
	else if ( nFlags & FLAG_HAS_PORT )
		return "PORT";
	else if ( nFlags & FLAG_HAS_SHIPYARD )
		return "SHIPYARD";
	else if ( nFlags & FLAG_HAS_HUB )
		return "HUB";
	else if ( nFlags & FLAG_BLOCKADE )
		return "<color;0000ff>BLOCKADE</color>";
	else if ( nFlags & FLAG_ENEMY )
		return "<color;0000ff>ENEMY DETECTED</color>";

	return "";
}

//----------------------------------------------------------------------------

// this routine searches the nodes for a mesh with the smallest radius, which is always the planets terrain
bool NounPlanet::findTerrain( BaseNode * pBaseNode, const Matrix33 & mFrame, const Vector3 & vPosition )
{
	Matrix33	mNodeFrame = mFrame;
	Vector3		vNodePosition = vPosition;

	Node * pNode = WidgetCast<Node>( pBaseNode );
	if ( pNode != NULL )
	{
		vNodePosition = (mFrame % pNode->position()) + vPosition;
		mNodeFrame = mFrame % pNode->frame();

		if ( WidgetCast<NodeComplexMesh>( pBaseNode ) || 
			WidgetCast<NodeComplexMesh2>( pBaseNode ) || 
			WidgetCast<NodeMesh>( pBaseNode ) )
		{
			// create a matrix without rotation so that if the object is rotated, it doesn't artificially expand the radius
			// because of the corners of the box
			Matrix33 mScale( Matrix33( true ) * mNodeFrame.scale() );
			BoxHull hull( pBaseNode->hull(), mScale, vNodePosition );

			float r = Max( Max( hull.max.x - hull.min.x, 
				hull.max.y - hull.min.y), 
				hull.max.z - hull.min.z);
			r = r * 0.5f;									// convert from diameter to radius

			if ( r < m_fRadius )
			{
				m_pTerrain = pBaseNode;
				m_fRadius = r;
			}
		}
	}

	// check the child nodes
	for(int i=0;i<pBaseNode->childCount();i++)
		findTerrain( pBaseNode->child(i), mNodeFrame, vNodePosition );

	// return true if we have a valid terrain node
	return m_pTerrain.valid();
}

//----------------------------------------------------------------------------
// EOF
